package de.unimannheim.ki.evaluation;

import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.map.hash.TObjectIntHashMap;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import weka.associations.FPGrowth;
import weka.core.Instance;
import weka.core.Instances;
import weka.experiment.InstanceQuery;

import de.unimannheim.ki.algorithmn.IComparator;
import de.unimannheim.ki.algorithmn.LikeAssociationJoint;
import de.unimannheim.ki.algorithmn.LikeDisJoint;
import de.unimannheim.ki.algorithmn.LikeHammingJoint;
import de.unimannheim.ki.algorithmn.NNAlgorithm;
import de.unimannheim.ki.algorithmn.Neighbour;
import de.unimannheim.ki.databaseentities.Musicuser;
import de.unimannheim.ki.databaseentities.Song;
import de.unimannheim.ki.usermanagement.DatabaseUserDAO;
import de.unimannheim.ki.usermanagement.FacebookUserHandler;
import de.unimannheim.ki.usermanagement.LastFMUserHandler;

public class EvaluationEnvironment {

	//UserSongs Rating matrix
		private int[][] userSongMatrix;
		
		//Mapping Components
		//userMapping
		
		TIntObjectHashMap<String> userMappingInttoFBID;
		//HashMap<String, Integer> userMappingFBIDtoInt;
		TObjectIntHashMap<String> userMappingFBIDtoInt;
		//HashMap<Integer, String> userMappingInttoFBID;
		//songMapping
		
		
		//HashMap<String, Integer> songMappingIDtoInt;
		//HashMap<Integer, String> songMappingInttoID;
		
		TObjectIntHashMap<String> songMappingIDtoInt;
		TIntObjectHashMap<String> songMappingInttoID;
		private FacebookUserHandler fbUserHandler;
		private LastFMUserHandler lastfmHandler;
		private DatabaseUserDAO userDao;
		//private FPGrowth model;
		
		public static final int LIKEJOINT = 0;
		public static final int LIKEHAMMINGJOINT =1;
		public static final int LIKEASSOCIATIONJOINT = 2;
		private HashMap<Integer,IComparator> comparators;
		private List<Musicuser> users;
		
		public EvaluationEnvironment(){

			comparators  = new HashMap<Integer,IComparator>();
			Instances data = null;
			InstanceQuery query = null;
				try {
				
					query = new InstanceQuery();
					query.setDatabaseURL("jdbc:mysql://localhost:3306/ebdb");
					System.out.println("DATABASE URL:::::"+query.getDatabaseURL());
					 query.setUsername("root");
					 query.setPassword("initial1");
					 query.setQuery("select * from testdata");
					query.connectToDatabase();
					data = query.retrieveInstances();
					
					// DataSource source = new DataSource("C:/Users/Alex/workSpaceProjekt/.metadata/.plugins/org.eclipse.wst.server.core/tmp1/wtpwebapps/my-music-search/sample5.csv");
					// DataSource source = new DataSource(csvfile);
					 System.out.println(data.size());
					//data = source.getDataSet();
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} 
				
		
			userDao = DatabaseUserDAO.getDAO();
			fbUserHandler = new FacebookUserHandler();
			lastfmHandler = new LastFMUserHandler();

			FPGrowth model = new FPGrowth();
			model.setDelta(0.01);
		    model.setLowerBoundMinSupport(0.01);
		    model.setMinMetric(0.5);
		     
			model.setFindAllRulesForSupportLevel(true);
			model.setNumRulesToFind(10000);
				try {
				model.buildAssociations(data);
				System.out.println("Model build! with rules:" + model.getAssociationRules().getNumRules());
			} catch (Exception e) {
				e.printStackTrace();
			}
			comparators.put(LIKEJOINT, new LikeDisJoint());
			comparators.put(LIKEHAMMINGJOINT,new LikeHammingJoint());
			comparators.put(LIKEASSOCIATIONJOINT, new LikeAssociationJoint(model, userDao));
			
			// Build the User song Matrix
			System.out.println("Build song user matrix");
			// First build Mapping for Songs and Users;
			userMappingFBIDtoInt =  new TObjectIntHashMap<String>();
			userMappingInttoFBID =  new TIntObjectHashMap<String>();
			
			
			songMappingIDtoInt = new TObjectIntHashMap<String>();
			songMappingInttoID = new TIntObjectHashMap<String>();
			users = userDao.getAllUserswithLastfm();
			List<Song> songs = userDao.getAllSongs();
			int x= 0;
			for (Song song : songs) {
				songMappingIDtoInt.put(song.getId(),x);
				songMappingInttoID.put(x, song.getId());
				x++;
			}
			System.out.println("Step 1 done");
			userSongMatrix = new int[songs.size()][users.size()];
			List<Song> usersongs;
			int i = 0;
			int songid;
			for (Musicuser musicuser : users) {
				if(musicuser.getLastfmid() == null) continue;
				userMappingFBIDtoInt.put(musicuser.getFbid(), i);
				usersongs = musicuser.getSongs();
				//get all songs for user and save them in the matrix
				for (Song song : usersongs) {
					songid = songMappingIDtoInt.get(song.getId());
					userSongMatrix[songid][i] = 1;
				}
				i ++;
			}
			System.out.println("Step 2 done + ++" + i);
		}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		// Split somehow the data
		EvaluationEnvironment eval = new EvaluationEnvironment();
		File f = new File("evalnew.csv");
		PrintWriter pw = null;
		try {
			pw = new PrintWriter(f);
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
		/*doexperiment(pw, eval, 0.02, 20, 0.2,0.8, false);
		
		doexperiment(pw, eval, 0.04, 20, 0.2,0.8, false);
		
		doexperiment(pw, eval, 0.06, 20, 0.2,0.8, false);
		
		doexperiment(pw, eval, 0.08, 20, 0.2,0.8, false);
		
		doexperiment(pw, eval, 0.10, 20, 0.2,0.8, false);*/
		
	/*	
		doexperiment(pw, eval, 0.02, 40, 0.2,0.8, false);
		
		doexperiment(pw, eval, 0.04, 40, 0.2,0.8, false);
		
		doexperiment(pw, eval, 0.06, 40, 0.2,0.8, false);
		
		doexperiment(pw, eval, 0.08, 40, 0.2,0.8, false);
		
		doexperiment(pw, eval, 0.10, 40, 0.2,0.8, false);
		
		
		doexperiment(pw, eval, 0.02, 60, 0.2,0.8, false);*/
		
		doexperiment(pw, eval, 0.04, 60, 0.2,0.8, true);
		
		doexperiment(pw, eval, 0.06, 60, 0.2,0.8, true);
		
		doexperiment(pw, eval, 0.08, 60, 0.2,0.8, true);
		
		//doexperiment(pw, eval, 0.10, 60, 0.2,0.8, false);
		


		//doexperiment(pw, eval, 0.02, 80, 0.2,0.8, false);
		
		doexperiment(pw, eval, 0.04, 80, 0.2,0.8, true);
		
		doexperiment(pw, eval, 0.06, 80, 0.2,0.8, true);
		
		doexperiment(pw, eval, 0.08, 80, 0.2,0.8, true);
		
		doexperiment(pw, eval, 0.10, 80, 0.2,0.8, true);
		
	
		pw.flush();
		pw.close();
		//nn.computeNeighbours(mu, allUsers)
		//nn.computeNeighbours(mu, allUsers)
		//compute confusion matrix
		
	}
	
	public static void doexperiment(PrintWriter pw, EvaluationEnvironment eval,double threshold, int kneighbours, double tester, double trainers, boolean cross){
		// just Feed the algorithmn with one part of data
				//split subset
				pw.println("Experiment with th=" +  threshold +"kneighbours = "+ kneighbours +" testers = " + tester);
				System.out.println("Experiment with th=" +  threshold +"kneighbours = "+ kneighbours +" testers = " + tester);
				
				int usersize = eval.users.size();
				//TODO do percentage
				System.out.println(usersize);
				
				int trainusersno =   (int) ((double)  ((double) (usersize))*trainers);
				//int testerusersno =   (int) ((double)  ((double) (usersize))*tester);
				List<Musicuser> testusers  = null;
				List<Musicuser> traningusers = null;
				System.out.println("trainers:" + trainusersno);
				if(!cross){
					 traningusers = eval.users.subList(0,trainusersno-1);
					testusers =  eval.users.subList(trainusersno,usersize-1 );
				}else {
					 traningusers = eval.users.subList(usersize-trainusersno, usersize-1);
					 testusers =  eval.users.subList(0,usersize-trainusersno);
				}
				// test result
				NNAlgorithm nn = new NNAlgorithm(eval.comparators.get(eval.LIKEJOINT));
				List<Neighbour> neighbours;
				RelevantNonRelevantLists lists = new RelevantNonRelevantLists();
				System.out.println("TESTER SIZE:"+testusers.size());
				for (Musicuser testuser : testusers ) {
					 //System.out.println(testuser.getFbid() +" -- " +testuser.getObjects().size() +" -- " + testuser.getSongs().size());
					 neighbours = nn.computeNeighbours(testuser, traningusers);
					 neighbours = neighbours.subList(0,kneighbours);
					 lists = eval.getrelatedSongsAnalysis(neighbours,threshold);
					 eval.comparesonglist(pw, testuser.getSongs(), lists.relevant,lists.nonrelevant);
				}
				
	}
	
	public void comparesonglist(PrintWriter pw, List<Song> testlist, ArrayList<Song> classlabellist, ArrayList<Song> notclasslabellist){
		
		//nonrelevant list ist allsongs - reclist
		//nonrelevant list
		ArrayList<Song> test = new ArrayList<Song>(testlist);
		ArrayList<Song> test2= (ArrayList<Song>) test.clone();
		
		test2.retainAll(classlabellist);

		//test2 = true positives list
		int tp = test2.size();
		ArrayList<Song> classlabel2 = (ArrayList<Song>) classlabellist.clone();
		classlabel2.removeAll(testlist);
		
		int fp = classlabel2.size();
		
		notclasslabellist.retainAll(testlist);
		int fn = notclasslabellist.size();
		
		//True positives: Schnittmenge userlist rec list
		//False positives: sind in reclist aber nicht in userlist
		//True negatives: sind nicht in recList und nicht in userlist
		//False negatives: sind userlist
		
		double precision = (double) ((double)tp) / ((double) tp + fp);
		double recall = (double) ((double)tp) / ((double) tp + fn);
		System.out.println("FN"+fn+" FP:" + fp +"   " + "TP:" + tp + " Precision = " + precision + "Recall = " + recall);
		//TODO write in CSV File
		pw.println(fn+", "+fp+", "+tp+", "+ precision +", " + recall);
	}
	
	public List<Song> getrelatedSongs(List<Neighbour> neighbours, double threshold){
		List<Song> recommended_songs = new ArrayList<Song>();
		int internaluserid;
		int internalsongid;
		
		double averagerating;
		int aggregatedrating = 0; 
		//numer of users
		int n = 0;
		//calculate the average rating for each song in the database
		for(Song song : userDao.getAllSongs()){
			internalsongid = songMappingIDtoInt.get(song.getId());
			
			for (Neighbour neighbour :neighbours) {
				//add up value from the song matrix
				aggregatedrating =  aggregatedrating +userSongMatrix [internalsongid] [userMappingFBIDtoInt.get(neighbour.getUser().getFbid())];
				n++;
			}
			
			averagerating = (double) ((double)aggregatedrating)/((double)n);
			//System.out.println(averagerating);
			n=0;
			aggregatedrating = 0; 
			if(averagerating > threshold){
				// get song for user
				recommended_songs.add(song);
			}
		}
		return recommended_songs;
	}
	
	public RelevantNonRelevantLists getrelatedSongsAnalysis(List<Neighbour> neighbours, double threshold){
		
		RelevantNonRelevantLists lists = new RelevantNonRelevantLists();
		List<Song> recommended_songs = new ArrayList<Song>();
		int internaluserid;
		int internalsongid;
		
		double averagerating;
		int aggregatedrating = 0; 
		//numer of users
		int n = 0;
		//calculate the average rating for each song in the database
		for(Song song : userDao.getAllSongs()){
			internalsongid = songMappingIDtoInt.get(song.getId());
			
			for (Neighbour neighbour :neighbours) {
				//add up value from the song matrix
				aggregatedrating =  aggregatedrating +userSongMatrix [internalsongid] [userMappingFBIDtoInt.get(neighbour.getUser().getFbid())];
				n++;
			}
			
			averagerating = (double) ((double)aggregatedrating)/((double)n);
			//System.out.println(averagerating);
			n=0;
			aggregatedrating = 0; 
			if(averagerating > threshold){
				// get song for user
				lists.relevant.add(song);
			}else {
				lists.nonrelevant.add(song);
			}
		}
		return lists;
		
	}

	
}
